Fix MutableInterfaceType not frozen in mutation-only return types#790
Merged
oojacoboo merged 1 commit intothecodingmachine:masterfrom Apr 7, 2026
Merged
Conversation
… types (thecodingmachine#308) Two code paths in RecursiveTypeMapper allowed MutableInterfaceType to escape the freeze mechanism: 1. findInterfaces() called the underlying typeMapper directly, creating orphaned instances never registered in TypeRegistry or frozen. 2. mapNameToType() handled MutableObjectType and input types but not MutableInterfaceType in its freeze logic.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #790 +/- ##
============================================
- Coverage 95.72% 94.81% -0.91%
- Complexity 1773 1852 +79
============================================
Files 154 175 +21
Lines 4586 4884 +298
============================================
+ Hits 4390 4631 +241
- Misses 196 253 +57 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
michael-georgiadis
approved these changes
Apr 7, 2026
michael-georgiadis
left a comment
There was a problem hiding this comment.
Good stuff! Thanks for adding 🫡
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #308 —
MutableInterfaceTypeinstances escape the freeze mechanism when used exclusively as mutation return types (no corresponding query returning the same interface). This causesRuntimeException: You must freeze() a MutableObjectType before fetching its fieldsduring schema validation, introspection, and query execution.Root Cause
Two code paths in
RecursiveTypeMapperallowedMutableInterfaceTypeto bypass freezing:1.
findInterfaces()created orphaned, unfrozen instancesfindInterfaces()called$this->typeMapper->mapClassToType()(the underlying type mapper) instead of going throughRecursiveTypeMapper::mapClassToType(). The underlying mapper (viaTypeGenerator) creates a newMutableInterfaceTypeinstance that is:TypeRegistry@ExtendTypeMeanwhile,
RecursiveTypeMapper::mapClassToType()later creates a different instance of the same type that is properly frozen and registered. This leaves webonyx holding a reference to the orphaned unfrozen instance (fromObjectType::getInterfaces()), while theTypeRegistrycontains the frozen one.Since webonyx's
TypeInfo::extractTypes()processesgetInterfaces()beforegetFields(), the unfrozen interface is discovered before the fields callback (which would trigger proper mapping) has a chance to run.2.
mapNameToType()didn't freezeMutableInterfaceTypeThe freeze logic in
mapNameToType()handledMutableObjectTypeand input types but had no branch forMutableInterfaceType. If an interface type was loaded by name through thetypeLoaderbefore being mapped throughmapClassToType(), it was returned inPENDINGstate.Fix
findInterfaces(): Now calls$this->mapClassToType()(RecursiveTypeMapper's own method) before retrieving the interface type, ensuring the instance is registered, extended, and frozen through the proper pipeline.mapNameToType(): Extended the freeze logic to also handleMutableInterfaceType, including applying type extensions before freezing — matching the existing pattern forMutableObjectType.Why not the
finalizeTypes()approach (#789)?PR #789 proposes brute-force freezing all registered types in
TypeRegistry::finalizeTypes()before schema creation. While well-intentioned, this doesn't address the root cause:findInterfaces()creates orphaned instances that are never registered in theTypeRegistry, sofinalizeTypes()misses them entirelyfinalizeTypes()has already run) would still be unfrozenmapNameToType()still wouldn't freezeMutableInterfaceTypeat runtimemapClassToType()applies before freezingTest Plan
MutationInterfaceFreezeTest:masterwith the exact error from Exception "You must freeze() a MutableObjectType before fetching its fields" gets thrown #308, pass with the fix